This work was presented in 2023 American Society of Clinical Oncology (ASCO) Annual Meeting. Available: https://doi.org/10.1200/JCO.2023.41.16_suppl.4022
The dataset used in this study was extracted from the database of a prospective clinical trial “Evaluation of the safety and sensitivity of 68Ga-DOTATOC PET/CT for imaging NET patients” at BC Cancer – Vancouver (NCT03583528, BC Cancer Agency Research Ethics Board Approval H17-00909). A total of 375 patients were enrolled in the trial between the start date, July 11, 2018, and the data query date, May 26, 2022.
library(knitr)
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, dpi = 200)
library(kableExtra)
library(tidyverse)
options(dplyr.summarise.inform = FALSE)
library(xlsx)
library(ggforce)
library(patchwork)
library(survival)
library(survminer)
library(tab)
library(consort)
data_raw <- read_tsv("NET DOTATOC May 26, 2022.txt", col_names = TRUE,
locale = locale(encoding = "UTF-16LE"))
10-Jun → 6-1020-Feb → 2-20Oct-40 → 10-40factordata_complete <- data_complete %>%
rowwise() %>%
mutate(
across(c(id_dob, contains("date", ignore.case = TRUE)), ~as.Date(.x, "%m/%d/%Y")),
across(c(path_num_ln_res, path_n_pos_ln, path_ki67, path_mitosis,
chartrev_path_ki67, chartrev_path_mitotic, lsrimg_num_les),
~ifelse(grepl(paste(month.abb, collapse = "|"), .x),
(function(x) {
num_spl <- str_split(x, "-", simplify = TRUE)
for (i in 1:length(num_spl)) {
num_spl[i] <- ifelse(!is.na(match(num_spl[i], month.abb)),
match(num_spl[i], month.abb), parse_number(num_spl[i]))
}
num_spl <- as.numeric(num_spl)
paste(min(num_spl), max(num_spl), sep = "-")
})(.x), .x)),
diag_prim_site = factor(diag_prim_site,
levels = c("Pancreas", "Small Intestine", "Lung",
"Rectum", "Stomach", "Cecum",
"Appendix", "Colon", "Liver",
"Other", "Unknown Primary")),
diag_histology = factor(diag_histology,
levels = c("Gastroenteropancreatic Neuroendocrine Tumors functioning and non-functioning",
"Sympathoadrenal System Tumors",
"Medullary thyroid cancer",
"Pituitary Adenoma",
"Medulloblastoma",
"Merkel Cell Carcinoma",
"Small Cell Lung Cancer (mainly primary tumors)",
"Meningioma",
"Any other NET/tumor with potential for overexpression of somatostatin receptors")))
Split the data into the following chunks according to database
setup:
dx_info: Diagnostic information. Contains selected
entries from “Patient ID Information”, “Diagnostic Information”, and
“Pathology Information at Diagnosis” formsblood_at_dx: “Blood Test Information at Diagnosis”
formsymp_n_relap:“Symptoms and Relapse Information”
formpath_chart_rv: “Pathology Chart Review” formimg_chart_rv: “Imaging Chart Review” formqual_img: “Qualitative Image Analysis - Local”
formquan_img: “Quantitative Analysis - Local” formdx_info <- data_complete %>%
filter(is.na(redcap_repeat_instrument)) %>%
select(study_id, diag_path_date:pathology_information_at_diagnosis_complete) %>%
discard(~all(is.na(.x))) %>%
map_df(~.x)
blood_at_dx <- data_complete %>%
filter(is.na(redcap_repeat_instrument)) %>%
select(study_id, blood_5hiaa:blood_test_information_at_diagnosis_complete) %>%
discard(~all(is.na(.x))) %>%
map_df(~.x)
symp_n_relap <- data_complete %>%
filter(is.na(redcap_repeat_instrument)) %>%
select(study_id, peptide_secretions___1:symptoms_and_relapse_information_complete) %>%
discard(~all(is.na(.x))) %>%
map_df(~.x)
path_chart_rv <- data_complete %>%
filter(redcap_repeat_instrument == "Pathology Chart Review") %>%
discard(~all(is.na(.x))) %>%
map_df(~.x)
img_chart_rv <- data_complete %>%
filter(redcap_repeat_instrument == "Imaging Chart Review") %>%
discard(~all(is.na(.x))) %>%
map_df(~.x)
qual_img <- data_complete %>%
filter(redcap_repeat_instrument == "Qualitative Image Analysis - Local") %>%
discard(~all(is.na(.x))) %>%
map_df(~.x)
quan_img <- data_complete %>%
filter(redcap_repeat_instrument == "Quantitative Analysis - Local") %>%
discard(~all(is.na(.x))) %>%
map_df(~.x)
Chart review was conducted when a questionable case was identified.
Any data entry errors found throughout the entire analysis were corrected in this step. (Codes are hidden here.)
Both Ki67 and mitosis results are used to determine tumour grade (in the next step). However, these data were entered into text fields and saved as strings. To convert the strings into a comparable format, two additional variables are created to capture the lower bound and upper bound of the result. Examples are as follows:
3: parse number, save
lower = 3 and upper = 3<5: parse number, save
lower = NA and upper = 5>20: parse number,
save lower = 20 and upper = NA5-10: parse both numbers, save
lower = 5 and upper = 10none,
no...found/detected/observed/present, etc.: save
lower = 0 and upper = 0not mentioned/specified or na:
save lower = NA and upper = NAdx_info <- dx_info %>%
rowwise() %>%
mutate(
path_ki67_parse_lower = case_when(grepl('not mentioned|not specified|na|n/a',
path_ki67, ignore.case = TRUE) ~ -1,
grepl('none|no.*found|no.*detected|no.*observed|no.*present',
path_ki67, ignore.case = TRUE) ~ 0,
grepl('<|less', path_ki67) ~ NA_real_,
grepl('-|&|to', path_ki67) ~ as.numeric(unlist(str_extract_all(path_ki67, "\\d+\\.*\\d*")))[1],
TRUE ~ as.numeric(str_extract(path_ki67, "\\d+\\.*\\d*"))),
path_ki67_parse_upper = case_when(grepl('none|not mentioned|not specified|na|n/a',
path_ki67, ignore.case = TRUE) ~ -1,
grepl('none|no.*found|no.*detected|no.*observed|no.*present',
path_ki67, ignore.case = TRUE) ~ 0,
grepl('>|more', path_ki67) ~ NA_real_,
grepl('-|&|to', path_ki67) ~ as.numeric(unlist(str_extract_all(path_ki67, "\\d+\\.*\\d*")))[2],
TRUE ~ as.numeric(str_extract(path_ki67, "\\d+\\.*\\d*"))),
path_mitosis_parse_lower = case_when(grepl('not mentioned|not specified|na|n/a',
path_mitosis, ignore.case = TRUE) ~ -1,
grepl('none|no.*found|no.*detected|no.*observed|no.*present',
path_mitosis, ignore.case = TRUE) ~ 0,
grepl('<|less', path_mitosis) ~ NA_real_,
grepl('-|&|to', path_mitosis) ~ as.numeric(unlist(str_extract_all(path_mitosis, "\\d+\\.*\\d*")))[1],
TRUE ~ as.numeric(str_extract(path_mitosis, "\\d+\\.*\\d*"))),
path_mitosis_parse_upper = case_when(grepl('not mentioned|not specified|na|n/a',
path_mitosis, ignore.case = TRUE) ~ -1,
grepl('none|no.*found|no.*detected|no.*observed|no.*present',
path_mitosis, ignore.case = TRUE) ~ 0,
grepl('>|less', path_mitosis) ~ NA_real_,
grepl('-|&|to', path_mitosis) ~ as.numeric(unlist(str_extract_all(path_mitosis, "\\d+\\.*\\d*")))[2],
TRUE ~ as.numeric(str_extract(path_mitosis, "\\d+\\.*\\d*"))),
across(c(path_mitosis_parse_lower, path_mitosis_parse_upper), # 10 HPF = 2.37 mm2 = 1.185 x 2 mm2
~ case_when(path_mitosis_unit == "/ 10 HPF" | .x == 0 ~ .x,
path_mitosis_unit == "/2mm2" ~ .x/1.185,
path_mitosis_unit == "Other" &
grepl('HPF', path_mitosis_unit_other, ignore.case = TRUE) ~
.x/(if_else(is.na(str_extract(path_mitosis_unit_other, "\\d+\\.*\\d*")), 1,
as.numeric(str_extract(path_mitosis_unit_other, "\\d+\\.*\\d*")))/10),
path_mitosis_unit == "Other" &
grepl('mm2', path_mitosis_unit_other, ignore.case = TRUE) ~
(.x/(if_else(is.na(str_extract(path_mitosis_unit_other, "\\d+\\.*\\d*(?=\\s?mm2)")), 1,
as.numeric(str_extract(path_mitosis_unit_other, "\\d+\\.*\\d*(?=\\s?mm2)")))/2))/1.185))) %>%
ungroup()
Determine the tumour grade based on Ki67 and mitosis results as per
WHO grading scheme:
(Source: https://www.researchgate.net/figure/Pancreatic-Neuroendocrine-Tumors-Grading-Scheme-According-to-World-Health-Organization_tbl1_317127151)
%. The following table further expands
the WHO grading scheme into criteria based on the values of
lower and upper variables.
|
upper
|
|||||||
|---|---|---|---|---|---|---|---|
| [0 , 3) | 3 | (3 , 20) | 20 | (20 , 100 | NA | ||
| lower | [0 , 3) | 1 | 1 | 1-2 | 1-2 | 1-3 | 1-3 |
| 3 | X | 2 | 2 | 2 | 2-3 | 2-3 | |
| (3 , 20) | X | X | 2 | 2 | 2-3 | 2-3 | |
| 20 | X | X | X | 2 | 3 | 3 | |
| (20 , 100 | X | X | X | X | 3 | 3 | |
| NA | 1 | 1 | 1-2 | 1-2 | 1-3 | NA | |
/10 HPF, /50 HPF,
/HPF, /mm2, /2 mm2/10 HPF with conversion
rate 10 HPF = 2.37 mm2 = 1.185 x 2 mm2."... low ...", grade 1 is assigned even
though both lower and upper are
NA.
|
upper
|
|||||||
|---|---|---|---|---|---|---|---|
| [0 , 2) | 2 | (2 , 20) | 20 | (20 , 100 | NA | ||
| lower | [0 , 2) | 1 | 1 | 1-2 | 1-2 | 1-3 | 1-3 |
| 2 | X | 2 | 2 | 2 | 2-3 | 2-3 | |
| (2 , 20) | X | X | 2 | 2 | 2-3 | 2-3 | |
| 20 | X | X | X | 2 | 3 | 3 | |
| (20 , 100 | X | X | X | X | 3 | 3 | |
| NA | 1 | 1 | 1-2 | 1-2 | 1-3 | NA | |
The above criteria are implemented here. The final tumor grade is primarily determined by the Ki67 or mitosis result, whichever is higher. If no test result is available, the original pathological grade is used.
dx_info <- dx_info %>%
mutate(
grade_ki67 = case_when(path_ki67_parse_lower == -1 | path_ki67_parse_upper == -1 ~ NA_character_,
(is.na(path_ki67_parse_lower) | path_ki67_parse_lower<3) &
path_ki67_parse_upper<=3 ~ "1",
(is.na(path_ki67_parse_lower) | path_ki67_parse_lower<3) &
(path_ki67_parse_upper>3 & path_ki67_parse_upper<=20) ~ "1-2",
(path_ki67_parse_lower>=3 & path_ki67_parse_lower<=20) &
(path_ki67_parse_upper>=3 & path_ki67_parse_upper<=20) ~ "2",
(path_ki67_parse_lower>=3 & path_ki67_parse_lower<20) &
(is.na(path_ki67_parse_upper)|path_ki67_parse_upper>20) ~ "2-3",
path_ki67_parse_lower>=20 &
(is.na(path_ki67_parse_upper)|path_ki67_parse_upper>20)~ "3",
path_ki67_parse_lower<3 &
(is.na(path_ki67_parse_upper)|path_ki67_parse_upper>20) ~ "1-3",
is.na(path_ki67_parse_lower) & path_ki67_parse_upper>20 ~ "1-3"),
grade_mitosis = case_when(path_mitosis_parse_lower == -1 | path_mitosis_parse_upper == -1 ~ NA_character_,
(is.na(path_mitosis_parse_lower) | path_mitosis_parse_lower<2) &
path_mitosis_parse_upper<=2 ~ "1",
(is.na(path_mitosis_parse_lower) | path_mitosis_parse_lower<2) &
(path_mitosis_parse_upper>2 & path_mitosis_parse_upper<=20) ~ "1-2",
(path_mitosis_parse_lower>=2 & path_mitosis_parse_lower<=20) &
(path_mitosis_parse_upper>=2 & path_mitosis_parse_upper<=20) ~ "2",
(path_mitosis_parse_lower>=2 & path_mitosis_parse_lower<20) &
(is.na(path_mitosis_parse_upper)|path_mitosis_parse_upper>20) ~ "2-3",
path_mitosis_parse_lower>=20 &
(is.na(path_mitosis_parse_upper)|path_mitosis_parse_upper>20)~ "3",
path_mitosis_parse_lower<2 &
(is.na(path_mitosis_parse_upper)|path_mitosis_parse_upper>20) ~ "1-3",
is.na(path_mitosis_parse_lower) & path_mitosis_parse_upper>20 ~ "1-3",
is.na(path_mitosis_parse_lower) & is.na(path_mitosis_parse_upper) &
grepl("low|inconspicuous|rare|uncommon|only 1", path_mitosis,
ignore.case = TRUE) ~ "1"),
grade_testresult = c("1", "1-2", "2", "1-3", "2-3", "3")[pmax(match(grade_ki67,
c("1", "1-2", "2", "1-3", "2-3", "3")),
match(grade_mitosis,
c("1", "1-2", "2", "1-3", "2-3", "3")),
na.rm = TRUE)],
grade_final = case_when(is.na(grade_testresult) & is.na(path_grade) ~ "Unknown Grade",
is.na(grade_testresult) & !is.na(path_grade) ~ paste0("Grade ", path_grade),
!is.na(grade_testresult) ~ paste0("Grade ", grade_testresult)))
If the resulting grade is indefinite (1-2, 2-3
or 1-3), chart review is conducted by an oncologist to
finalize a grade.
1 or 2 AND Differentiation
NA: update Differentiation
Well DifferentiatedPoorly Differentiated AND Grade
NA: update Grade 3dx_info <- dx_info %>%
left_join(img_chart_rv %>%
mutate(across(chartrev_image_pos_area___7:chartrev_image_pos_area___14, ~if_else(str_detect(., "Unchecked$"), 0, 1)),
met_status = rowSums(across(chartrev_image_pos_area___7:chartrev_image_pos_area___14))) %>%
group_by(study_id) %>%
summarize(img_met_status = if_else(sum(met_status) > 0, "Yes", "No")),
by = "study_id") %>%
mutate(
met_status_final = case_when(is.na(path_met_status) | path_met_status == "Not Specified" ~ img_met_status,
path_met_status == "No" & img_met_status == "No" ~ "No",
path_met_status == "Yes" | img_met_status == "Yes" ~ "Yes"),
diag_differentiation = if_else(grade_final %in% c("Grade 1", "Grade 2") &
is.na(diag_differentiation),
"Well Differentiated", diag_differentiation),
grade_final = if_else(diag_differentiation == "Poorly Differentiated" &
is.na(grade_final),
"Grade 3", grade_final),
grade_final = case_when(grade_final == "Grade 3" &
diag_differentiation == "Poorly Differentiated" ~
"Grade 3, Poorly Diff.",
grade_final == "Grade 3" &
diag_differentiation == "Well Differentiated" ~
"Grade 3, Well Diff.",
grade_final == "Grade 3" &
is.na(diag_differentiation) ~
"Grade 3, Unknown Diff.",
TRUE ~ grade_final) %>%
factor(levels = c("Grade 1", "Grade 2", "Grade 3, Well Diff.",
"Grade 3, Poorly Diff.", "Grade 3, Unknown Diff.",
"Unknown Grade")),
hist_group = case_when(grepl('NET|Neuroendocrine', diag_histology, ignore.case = TRUE) ~ "NET",
grepl('Merkel', diag_histology, ignore.case = TRUE) ~ "Merkel",
grepl('Small Cell', diag_histology, ignore.case = TRUE) ~ "Small Cell",
is.na(diag_histology) ~ "NA",
TRUE ~ "Other") %>%
factor(levels = c("NET", "Merkel", "Small Cell", "Other", "NA")),
age_at_diagnosis = as.numeric(diag_path_date - id_dob)/365.25)
krenning_LA_SSA <- read.xlsx("LA-SSA Krenning Score No. of Lesions.xlsx",
1, header = TRUE, check.names = FALSE) %>%
select(`Study ID`, `Date of Scan`, `No. of Lesions`, `Krenning Score`) %>%
rename(study_id = 1, qual_exam_date_LA_SSA = 2,
num_les_LA_SSA = 3, qual_krenning_LA_SSA = 4) %>%
mutate(num_les_LA_SSA = if_else(grepl("no lesion", num_les_LA_SSA, ignore.case = TRUE),
NA_character_, num_les_LA_SSA)) %>%
filter(study_id <= 375)
krenning_sarah <- read.xlsx("LA-SSA Krenning Score No. of Lesions.xlsx",
2, header = TRUE, check.names = FALSE) %>%
rename(study_id = 1, qual_krenning_sarah = 2)
krenning_KS <- read.xlsx("KS_170_256.xlsx",
1, header = TRUE, check.names = FALSE) %>%
rename(study_id = 1, qual_krenning_KS = 2) %>%
filter(study_id != "186B") %>%
mutate(study_id = as.double(study_id))
dx_info <- dx_info %>%
left_join(qual_img %>%
filter(!is.na(qual_exam_date)) %>%
group_by(study_id) %>%
summarize(scan_date_dot1 = min(qual_exam_date[qual_tracer == "68Ga-DOTATOC"], na.rm = TRUE),
interp_date_dot1 = qual_interp_date[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1],
interp_name_dot1 = qual_interp_name[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1],
img_quality_dot1 = qual_img_quality[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1],
overall_assess_dot1 = qual_overall_assess[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1],
certainty_dot1 = qual_diag_certainty[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1],
num_lesion_dot1 = lsrimg_num_les[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1],
local_dot1 = (qual_les_site___1[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1] == "Checked"),
regional_dot1 = (qual_les_site___2[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1] == "Checked"),
distant_bone_dot1 = (qual_les_site___3[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1] == "Checked"),
distant_lung_dot1 = (qual_les_site___4[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1] == "Checked"),
distant_liver_dot1 = (qual_les_site___5[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1] == "Checked"),
distant_other_dot1 = (qual_les_site___98[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1] == "Checked"),
distant_other_spec_dot1 = qual_les_site_other[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1],
krenning_dot1 = qual_krenning[qual_tracer == "68Ga-DOTATOC" &
qual_exam_date == scan_date_dot1])) %>%
left_join(qual_img %>%
filter(!is.na(qual_exam_date)) %>%
group_by(study_id) %>%
summarize(scan_date_fdg1 = min(qual_exam_date[qual_tracer == "18F-FDG"], na.rm = TRUE),
interp_date_fdg1 = qual_interp_date[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1],
interp_name_fdg1 = qual_interp_name[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1],
img_quality_fdg1 = qual_img_quality[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1],
overall_assess_fdg1 = qual_overall_assess[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1],
certainty_fdg1 = qual_diag_certainty[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1],
num_lesion_fdg1 = lsrimg_num_les[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1],
local_fdg1 = (qual_les_site___1[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1] == "Checked"),
regional_fdg1 = (qual_les_site___2[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1] == "Checked"),
distant_bone_fdg1 = (qual_les_site___3[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1] == "Checked"),
distant_lung_fdg1 = (qual_les_site___4[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1] == "Checked"),
distant_liver_fdg1 = (qual_les_site___5[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1] == "Checked"),
distant_other_fdg1 = (qual_les_site___98[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1] == "Checked"),
distant_other_spec_fdg1 = qual_les_site_other[qual_tracer == "18F-FDG" &
qual_exam_date == scan_date_fdg1])) %>%
left_join(krenning_LA_SSA, by = "study_id") %>%
left_join(krenning_sarah, by = "study_id") %>%
left_join(krenning_KS, by = "study_id") %>%
mutate(scan_date_dot1 = if_else(is.na(scan_date_dot1), qual_exam_date_LA_SSA, scan_date_dot1),
num_lesion_dot1 = if_else(is.na(num_lesion_dot1), num_les_LA_SSA, num_lesion_dot1),
krenning_dot1 = case_when(!is.na(krenning_dot1) ~ krenning_dot1,
!is.na(qual_krenning_LA_SSA) ~ qual_krenning_LA_SSA,
!is.na(qual_krenning_sarah) ~ qual_krenning_sarah,
!is.na(qual_krenning_KS) ~ qual_krenning_KS,
TRUE ~ krenning_dot1),
krenning_dot1 = if_else(overall_assess_dot1 == "Negative", 0, krenning_dot1),
overall_assess_dot1 = case_when(krenning_dot1 == 0 ~ "Negative",
is.na(overall_assess_dot1) & !is.na(num_lesion_dot1) ~ "Positive",
TRUE ~ overall_assess_dot1),
overall_assess_dot1 = factor(overall_assess_dot1, levels = c("Negative", "Positive")),
overall_assess_fdg1 = factor(overall_assess_fdg1, levels = c("Negative", "Positive")),
assess_group = case_when(overall_assess_dot1 == "Positive" & overall_assess_fdg1 == "Positive" ~ "DOTATOC+ FDG+",
overall_assess_dot1 == "Positive" & overall_assess_fdg1 == "Negative" ~ "DOTATOC+ FDG-",
overall_assess_dot1 == "Negative" & overall_assess_fdg1 == "Positive" ~ "DOTATOC- FDG+",
overall_assess_dot1 == "Negative" & overall_assess_fdg1 == "Negative" ~ "DOTATOC- FDG-") %>%
factor(levels = c("DOTATOC+ FDG+", "DOTATOC+ FDG-", "DOTATOC- FDG+", "DOTATOC- FDG-"))) %>%
select(-qual_exam_date_LA_SSA, -num_les_LA_SSA, -qual_krenning_LA_SSA,
-qual_krenning_sarah, -qual_krenning_KS)
dx_info <- dx_info %>%
left_join(qual_img %>%
filter(!is.na(qual_exam_date) | !is.na(qual_overall_assess) | !is.na(lsrimg_num_les)) %>%
group_by(study_id) %>%
summarize(earliest_scan_date = min(qual_exam_date, na.rm = TRUE),
num_dot = sum(qual_tracer == "68Ga-DOTATOC"),
num_dot_positive = sum(qual_tracer == "68Ga-DOTATOC" &
qual_overall_assess == "Positive"),
num_fdg = sum(qual_tracer == "18F-FDG"),
num_fdg_positive = sum(qual_tracer == "18F-FDG" &
qual_overall_assess == "Positive"))) %>%
mutate(diagnosis_to_scan_day = if_else(earliest_scan_date - diag_path_date < 0,
0, as.numeric(earliest_scan_date - diag_path_date)),
duration_dot1_fdg1 = abs(as.numeric(scan_date_dot1 - scan_date_fdg1)),
across(c(num_dot, num_dot_positive, num_fdg, num_fdg_positive),
~if_else(is.na(.x), 0L, .x)),
pos_to_scan_ratio_dot = if_else(num_dot == 0, "No DOTATOC scan", str_c(num_dot_positive, '/', num_dot)),
pos_to_scan_ratio_fdg = if_else(num_fdg == 0, "No FDG scan", str_c(num_fdg_positive, '/', num_fdg)),
num_lesion_dot1 = if_else(overall_assess_dot1 == "Negative", "0", num_lesion_dot1) %>%
factor(levels = c("0", "1", "2", "3", "4", "5", "6-10", ">10")),
num_lesion_fdg1 = if_else(overall_assess_fdg1 == "Negative", "0", num_lesion_fdg1) %>%
factor(levels = c("0", "1", "2", "3", "4", "5", "6-10", ">10"))) %>%
left_join(qual_img %>%
filter(!is.na(qual_overall_assess)) %>%
select(study_id, qual_tracer, qual_overall_assess) %>%
pivot_wider(names_from = qual_tracer,
values_from = qual_overall_assess,
values_fn = ~paste0(.x, collapse = " -> ")) %>%
rename(overall_assess_alldot = `68Ga-DOTATOC`,
overall_assess_allfdg = `18F-FDG`))
The survival status of each patient was reviewed and updated on Jan 24, 2023.
osdata_raw <- read_csv("Dotatoc Survival (JL JP MZ 1.24.2023)_deidentified.csv", col_names = TRUE) %>%
rename(study_id = `Study ID`,
last_contact = `Date of Death or Last Contact`,
status = `Death=1`) %>%
mutate(study_id = parse_number(str_replace_all(study_id, "DOTATOC-01-", "")),
last_contact = as.Date(last_contact, "%d-%b-%y"))
dx_info <- dx_info %>%
left_join(osdata_raw) %>%
mutate(survival_day = as.numeric(last_contact - diag_path_date),
survival_year = survival_day/365.25)
net_data <- dx_info %>%
filter(hist_group == "NET")
gepnet_data <- net_data %>%
filter(diag_histology == "Gastroenteropancreatic Neuroendocrine Tumors functioning and non-functioning")
net_met_data <- net_data %>%
filter(met_status_final == "Yes")
gepnet_met_data <- net_met_data %>%
filter(diag_histology == "Gastroenteropancreatic Neuroendocrine Tumors functioning and non-functioning",
pos_to_scan_ratio_dot != "No DOTATOC scan",
pos_to_scan_ratio_fdg != "No FDG scan")
count_total_func <- function(df, description, new_table = FALSE) {
summary_tb <- tibble_row(Parameter = description,
df %>% summarize(Total = n()))
if (by_grade) {
summary_tb <- summary_tb %>%
bind_cols(df %>%
group_by(grade_final) %>%
summarize(n = n()) %>%
filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
pivot_wider(names_from = grade_final, values_from = n)) %>%
mutate(across(contains("Grade"), ~ paste0(.x, ' (', sprintf("%.1f", .x/Total*100), ')')))
}
summary_tb <- summary_tb %>%
mutate(Total = as.character(Total))
if (new_table) pack_row_index <<- c()
pack_row_index <<- setNames(c(pack_row_index, nrow(summary_tb)),
c(names(pack_row_index), " "))
return(summary_tb)
}
median_func <- function(df, variable, description, new_table = FALSE) {
summary_tb <- tibble_row(Parameter = description,
df %>%
summarize(med = as.integer(median({{variable}}, na.rm = TRUE)),
perc25 = as.integer(quantile({{variable}}, 0.25, na.rm = TRUE)),
perc75 = as.integer(quantile({{variable}}, 0.75, na.rm = TRUE)))) %>%
mutate(Total = str_c(med, ' (', perc25, '-', perc75, ')')) %>%
select(-med, -perc25, -perc75)
if (by_grade) {
summary_tb <- summary_tb %>%
bind_cols(df %>%
group_by(grade_final) %>%
summarize(med = as.integer(median({{variable}}, na.rm = TRUE)),
perc25 = as.integer(quantile({{variable}}, 0.25, na.rm = TRUE)),
perc75 = as.integer(quantile({{variable}}, 0.75, na.rm = TRUE))) %>%
mutate(med = str_c(med, ' (', perc25, '-', perc75, ')')) %>%
select(-perc25, -perc75) %>%
filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
pivot_wider(names_from = grade_final, values_from = med))
}
if (new_table) pack_row_index <<- c()
pack_row_index <<- setNames(c(pack_row_index, nrow(summary_tb)),
c(names(pack_row_index), " "))
return(summary_tb)
}
count_bygroup_func <- function(df, group_var, description, new_table = FALSE) {
summary_tb <- df %>%
group_by({{group_var}}) %>%
summarize(Total = as.integer(n())) %>%
filter(if (na_groupvar_remove) !is.na({{group_var}}) else TRUE) %>%
rename(Parameter = {{group_var}}) %>%
mutate(Total = if_else(is.na(Total), as.character(0L),
paste0(Total, ' (', sprintf("%.1f", Total/sum(Total)*100), ')')),
Parameter = as.character(Parameter))
if (by_grade) {
summary_tb <- summary_tb %>%
full_join(df %>%
group_by(grade_final, {{group_var}}) %>%
summarize(n = n()) %>%
filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
filter(if (na_groupvar_remove) !is.na({{group_var}}) else TRUE) %>%
rename(Parameter = {{group_var}}) %>%
mutate(Parameter = as.character(Parameter)) %>%
pivot_wider(names_from = grade_final, values_from = n)) %>%
mutate(across(contains("Grade"), ~
if_else(is.na(.x), as.character(0L),
paste0(.x, ' (', sprintf("%.1f", .x/sum(.x, na.rm = TRUE)*100), ')'))))
}
if (new_table) pack_row_index <<- c()
pack_row_index <<- setNames(c(pack_row_index, nrow(summary_tb)),
c(names(pack_row_index), description))
return(summary_tb)
}
count_pack_func <- function(df_list, description_list, pack_description,
sum_df = NULL, new_table = FALSE) {
summary_tb <- tibble()
for (i in 1:length(df_list)) {
summary_tb <- summary_tb %>%
bind_rows(tibble(Parameter = as.character(description_list[[i]]),
df_list[[i]] %>% summarize(Total = n())))
}
total_num <- if_else(is.null(sum_df), as.integer(sum(summary_tb$Total)), nrow(sum_df))
summary_tb <- summary_tb %>%
mutate(Total = if_else(is.na(Total), as.character(0L),
paste0(Total, ' (', sprintf("%.1f", Total/total_num*100), ')')))
if (by_grade) {
by_grade_tb <- tibble()
for (i in 1:length(df_list)) {
by_grade_tb <- by_grade_tb %>%
bind_rows(df_list[[i]] %>%
group_by(grade_final) %>%
summarize(n = n()) %>%
filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
pivot_wider(names_from = grade_final, values_from = n))
}
if (is.null(sum_df)) {
total_num <- by_grade_tb %>%
summarize(across(everything(), ~sum(.x, na.rm = TRUE)))
} else {
total_num <- sum_df %>%
group_by(grade_final) %>%
summarize(n = n()) %>%
filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
pivot_wider(names_from = grade_final, values_from = n)
}
by_grade_tb <- by_grade_tb %>%
mutate(across(contains("Grade"), ~
if_else(is.na(.x), as.character(0L),
paste0(.x, ' (', sprintf("%.1f", .x/total_num$.x*100), ')'))))
summary_tb <- bind_cols(summary_tb, by_grade_tb)
}
if (new_table) pack_row_index <<- c()
pack_row_index <<- setNames(c(pack_row_index, nrow(summary_tb)),
c(names(pack_row_index), pack_description))
return(summary_tb)
}
| Parameter | Total | Grade 1 | Grade 2 | Grade 3, Well Diff. | Grade 3, Poorly Diff. | Grade 3, Unknown Diff. | Unknown Grade |
|---|---|---|---|---|---|---|---|
| Number of Subjects | 375 | 148 (39.5) | 131 (34.9) | 16 (4.3) | 10 (2.7) | 1 (0.3) | 69 (18.4) |
| Sex | |||||||
| F | 187 (49.9) | 72 (48.6) | 66 (50.4) | 7 (43.8) | 3 (30.0) | 0 | 39 (56.5) |
| M | 188 (50.1) | 76 (51.4) | 65 (49.6) | 9 (56.2) | 7 (70.0) | 1 (100.0) | 30 (43.5) |
| Median Age at Diagnosis | 59 (48-69) | 59 (50-67) | 60 (47-69) | 65 (40-72) | 65 (58-70) | 72 (72-72) | 45 (37-61) |
| Median Time from Diagnosis to Scan (Days) | 475 (92-1637) | 487 (92-1804) | 676 (111-1644) | 193 (30-692) | 88 (61-618) | 322 (322-322) | 199 (129-2509) |
| Metastasis | |||||||
| No | 147 (39.2) | 57 (38.5) | 33 (25.2) | 2 (12.5) | 2 (20.0) | 0 | 53 (76.8) |
| Yes | 228 (60.8) | 91 (61.5) | 98 (74.8) | 14 (87.5) | 8 (80.0) | 1 (100.0) | 16 (23.2) |
| Histology | |||||||
| Gastroenteropancreatic Neuroendocrine Tumors functioning and non-functioning | 248 (78.7) | 120 (83.9) | 104 (81.9) | 15 (93.8) | 5 (55.6) | 0 | 4 (21.1) |
| Sympathoadrenal System Tumors | 16 (5.1) | 6 (4.2) | 5 (3.9) | 1 (6.2) | 0 | 0 | 4 (21.1) |
| Medullary thyroid cancer | 13 (4.1) | 0 | 1 (0.8) | 0 | 1 (11.1) | 0 | 11 (57.9) |
| Pituitary Adenoma | 1 (0.3) | 1 (0.7) | 0 | 0 | 0 | 0 | 0 |
| Merkel Cell Carcinoma | 2 (0.6) | 0 | 0 | 0 | 1 (11.1) | 1 (100.0) | 0 |
| Small Cell Lung Cancer (mainly primary tumors) | 1 (0.3) | 0 | 0 | 0 | 1 (11.1) | 0 | 0 |
| Meningioma | 1 (0.3) | 1 (0.7) | 0 | 0 | 0 | 0 | 0 |
| Any other NET/tumor with potential for overexpression of somatostatin receptors | 33 (10.5) | 15 (10.5) | 17 (13.4) | 0 | 1 (11.1) | 0 | 0 |
| Histology = NA | |||||||
| No cancer diagnosis | 54 (88.5) | 5 (100.0) | 2 (50.0) | 0 | 0 | 0 | 47 (92.2) |
| Other type of cancer | 7 (11.5) | 0 | 2 (50.0) | 0 | 1 (100.0) | 0 | 4 (7.8) |
| Parameter | Total | Grade 1 | Grade 2 | Grade 3, Well Diff. | Grade 3, Poorly Diff. | Unknown Grade |
|---|---|---|---|---|---|---|
| Number of Subjects | 165 | 73 (44.2) | 75 (45.5) | 13 (7.9) | 2 (1.2) | 2 (1.2) |
| Sex | ||||||
| F | 78 (47.3) | 32 (43.8) | 38 (50.7) | 5 (38.5) | 2 (100.0) | 1 (50.0) |
| M | 87 (52.7) | 41 (56.2) | 37 (49.3) | 8 (61.5) | 0 | 1 (50.0) |
| Median Age at Diagnosis | 64 (52-71) | 61 (52-69) | 65 (55-72) | 65 (40-75) | 70 (69-71) | 51 (45-57) |
| Median Time from Diagnosis to Scan (Days) | 665 (120-1863) | 491 (120-2165) | 814 (137-1822) | 67 (27-665) | 1086 (1065-1107) | 1334 (752-1916) |
| Median Time Between Baseline DOTATOC and FDG Scans (Days) | 4 (1-11) | 4 (1-11) | 4 (1-11) | 5 (3-7) | 20 (10-30) | 12 (9-15) |
| Median Follow-Up Time (Days) | 1473 (747-2650) | 1563 (846-2983) | 1564 (777-2669) | 811 (495-1099) | 1476 (1467-1484) | 2142 (1408-2876) |
| Parameter | Total | Grade 1 | Grade 2 | Grade 3, Well Diff. | Grade 3, Poorly Diff. | Unknown Grade |
|---|---|---|---|---|---|---|
| Total Number of metastatic GEP NET cases | 165 | 73 (44.2) | 75 (45.5) | 13 (7.9) | 2 (1.2) | 2 (1.2) |
| Primary Site | ||||||
| Pancreas | 37 (22.4) | 15 (20.5) | 15 (20.0) | 7 (53.8) | 0 | 0 |
| Small Intestine | 86 (52.1) | 47 (64.4) | 37 (49.3) | 1 (7.7) | 0 | 1 (50.0) |
| Rectum | 7 (4.2) | 1 (1.4) | 3 (4.0) | 1 (7.7) | 2 (100.0) | 0 |
| Stomach | 2 (1.2) | 0 | 2 (2.7) | 0 | 0 | 0 |
| Cecum | 5 (3.0) | 2 (2.7) | 2 (2.7) | 0 | 0 | 1 (50.0) |
| Appendix | 1 (0.6) | 0 | 1 (1.3) | 0 | 0 | 0 |
| Colon | 2 (1.2) | 0 | 2 (2.7) | 0 | 0 | 0 |
| Liver | 1 (0.6) | 0 | 0 | 1 (7.7) | 0 | 0 |
| Unknown Primary | 24 (14.5) | 8 (11.0) | 13 (17.3) | 3 (23.1) | 0 | 0 |
| Differentiation at Diagnosis | ||||||
| Poorly Differentiated | 2 (1.2) | 0 | 0 | 0 | 2 (100.0) | 0 |
| Well Differentiated | 162 (98.8) | 73 (100.0) | 75 (100.0) | 13 (100.0) | 0 | 1 (100.0) |
| Number of DOTATOC-avid Scan(s) / Total Number of DOTATOC Scan(s) Received | ||||||
| 0/1 | 7 (4.2) | 3 (4.1) | 4 (5.3) | 0 | 0 | 0 |
| 1/1 | 145 (87.9) | 65 (89.0) | 64 (85.3) | 12 (92.3) | 2 (100.0) | 2 (100.0) |
| 1/2 | 1 (0.6) | 0 | 1 (1.3) | 0 | 0 | 0 |
| 2/2 | 10 (6.1) | 4 (5.5) | 5 (6.7) | 1 (7.7) | 0 | 0 |
| 4/4 | 2 (1.2) | 1 (1.4) | 1 (1.3) | 0 | 0 | 0 |
| Number of FDG-avid Scan(s) / Total Number of FDG Scan(s) Received | ||||||
| 0/1 | 74 (44.8) | 42 (57.5) | 31 (41.3) | 0 | 0 | 1 (50.0) |
| 0/2 | 2 (1.2) | 2 (2.7) | 0 | 0 | 0 | 0 |
| 0/4 | 1 (0.6) | 0 | 1 (1.3) | 0 | 0 | 0 |
| 1/1 | 81 (49.1) | 26 (35.6) | 40 (53.3) | 12 (92.3) | 2 (100.0) | 1 (50.0) |
| 1/2 | 1 (0.6) | 1 (1.4) | 0 | 0 | 0 | 0 |
| 2/2 | 6 (3.6) | 2 (2.7) | 3 (4.0) | 1 (7.7) | 0 | 0 |
| Parameter | Total | Grade 1 | Grade 2 | Grade 3, Well Diff. | Grade 3, Poorly Diff. | Unknown Grade |
|---|---|---|---|---|---|---|
| Baseline DOTATOC - Assessment | ||||||
| Negative | 7 (4.2) | 3 (4.1) | 4 (5.3) | 0 | 0 | 0 |
| Positive | 158 (95.8) | 70 (95.9) | 71 (94.7) | 13 (100.0) | 2 (100.0) | 2 (100.0) |
| Baseline DOTATOC - Number of Lesions in Positive Cases | ||||||
| 1 | 11 (7.0) | 10 (14.3) | 1 (1.4) | 0 | 0 | 0 |
| 2 | 11 (7.0) | 4 (5.7) | 7 (9.9) | 0 | 0 | 0 |
| 3 | 7 (4.4) | 4 (5.7) | 3 (4.2) | 0 | 0 | 0 |
| 4 | 4 (2.5) | 3 (4.3) | 1 (1.4) | 0 | 0 | 0 |
| 5 | 9 (5.7) | 3 (4.3) | 4 (5.6) | 1 (7.7) | 1 (50.0) | 0 |
| 6-10 | 26 (16.5) | 14 (20.0) | 11 (15.5) | 0 | 0 | 1 (50.0) |
| >10 | 90 (57.0) | 32 (45.7) | 44 (62.0) | 12 (92.3) | 1 (50.0) | 1 (50.0) |
| Baseline DOTATOC - Site(s) of Lesion in Positive Cases | ||||||
| Local Relapse | 17 (10.8) | 5 (7.1) | 9 (12.7) | 3 (23.1) | 0 | 0 |
| Regional Nodes | 86 (54.4) | 39 (55.7) | 35 (49.3) | 10 (76.9) | 1 (50.0) | 1 (50.0) |
| Distant Metastases (Bone) | 49 (31.0) | 16 (22.9) | 24 (33.8) | 8 (61.5) | 1 (50.0) | 0 |
| Distant Metastases (Lung) | 9 (5.7) | 2 (2.9) | 6 (8.5) | 0 | 1 (50.0) | 0 |
| Distant Metastases (Liver) | 117 (74.1) | 51 (72.9) | 53 (74.6) | 11 (84.6) | 1 (50.0) | 1 (50.0) |
| Distant Metastases (Other) | 81 (51.3) | 32 (45.7) | 38 (53.5) | 7 (53.8) | 2 (100.0) | 2 (100.0) |
| Baseline DOTATOC - Krenning Score in Positive Cases | ||||||
| 1 | 2 (1.3) | 1 (1.4) | 0 | 0 | 1 (50.0) | 0 |
| 2 | 7 (4.4) | 2 (2.9) | 4 (5.6) | 1 (7.7) | 0 | 0 |
| 3 | 58 (36.7) | 28 (40.0) | 24 (33.8) | 5 (38.5) | 1 (50.0) | 0 |
| 4 | 91 (57.6) | 39 (55.7) | 43 (60.6) | 7 (53.8) | 0 | 2 (100.0) |
| Parameter | Total | Grade 1 | Grade 2 | Grade 3, Well Diff. | Grade 3, Poorly Diff. | Unknown Grade |
|---|---|---|---|---|---|---|
| Baseline FDG - Assessment | ||||||
| Negative | 76 (46.1) | 44 (60.3) | 31 (41.3) | 0 | 0 | 1 (50.0) |
| Positive | 89 (53.9) | 29 (39.7) | 44 (58.7) | 13 (100.0) | 2 (100.0) | 1 (50.0) |
| Baseline FDG - Number of Lesions in Positive Cases | ||||||
| 1 | 23 (25.8) | 10 (34.5) | 11 (25.0) | 1 (7.7) | 0 | 1 (100.0) |
| 2 | 14 (15.7) | 5 (17.2) | 8 (18.2) | 1 (7.7) | 0 | 0 |
| 3 | 5 (5.6) | 2 (6.9) | 3 (6.8) | 0 | 0 | 0 |
| 4 | 4 (4.5) | 1 (3.4) | 2 (4.5) | 1 (7.7) | 0 | 0 |
| 5 | 8 (9.0) | 4 (13.8) | 2 (4.5) | 1 (7.7) | 1 (50.0) | 0 |
| 6-10 | 13 (14.6) | 6 (20.7) | 5 (11.4) | 1 (7.7) | 1 (50.0) | 0 |
| >10 | 22 (24.7) | 1 (3.4) | 13 (29.5) | 8 (61.5) | 0 | 0 |
| Baseline FDG - Site(s) of Lesion in Positive Cases | ||||||
| Local Relapse | 20 (22.5) | 7 (24.1) | 6 (13.6) | 7 (53.8) | 0 | 0 |
| Regional Nodes | 36 (40.4) | 13 (44.8) | 14 (31.8) | 8 (61.5) | 1 (50.0) | 0 |
| Distant Metastases (Bone) | 18 (20.2) | 1 (3.4) | 9 (20.5) | 8 (61.5) | 0 | 0 |
| Distant Metastases (Lung) | 4 (4.5) | 0 | 2 (4.5) | 1 (7.7) | 1 (50.0) | 0 |
| Distant Metastases (Liver) | 47 (52.8) | 13 (44.8) | 25 (56.8) | 8 (61.5) | 1 (50.0) | 0 |
| Distant Metastases (Other) | 33 (37.1) | 7 (24.1) | 19 (43.2) | 4 (30.8) | 2 (100.0) | 1 (100.0) |
| Parameter | Total | Grade 1 | Grade 2 | Grade 3, Well Diff. | Grade 3, Poorly Diff. | Unknown Grade |
|---|---|---|---|---|---|---|
| Baseline Scan Result | ||||||
| DOTATOC+ FDG+ | 88 (53.3) | 29 (39.7) | 43 (57.3) | 13 (100.0) | 2 (100.0) | 1 (50.0) |
| DOTATOC+ FDG- | 70 (42.4) | 41 (56.2) | 28 (37.3) | 0 | 0 | 1 (50.0) |
| DOTATOC- FDG+ | 1 (0.6) | 0 | 1 (1.3) | 0 | 0 | 0 |
| DOTATOC- FDG- | 6 (3.6) | 3 (4.1) | 3 (4.0) | 0 | 0 | 0 |
| DOTATOC Krenning Score with FDG Negative | ||||||
| 0 | 6 (7.9) | 3 (6.8) | 3 (9.7) | 0 | 0 | 0 |
| 1 | 1 (1.3) | 1 (2.3) | 0 | 0 | 0 | 0 |
| 2 | 3 (3.9) | 2 (4.5) | 1 (3.2) | 0 | 0 | 0 |
| 3 | 34 (44.7) | 22 (50.0) | 12 (38.7) | 0 | 0 | 0 |
| 4 | 32 (42.1) | 16 (36.4) | 15 (48.4) | 0 | 0 | 1 (100.0) |
| DOTATOC Krenning Score with FDG Positive | ||||||
| 0 | 1 (1.1) | 0 | 1 (2.3) | 0 | 0 | 0 |
| 1 | 1 (1.1) | 0 | 0 | 0 | 1 (50.0) | 0 |
| 2 | 4 (4.5) | 0 | 3 (6.8) | 1 (7.7) | 0 | 0 |
| 3 | 24 (27.0) | 6 (20.7) | 12 (27.3) | 5 (38.5) | 1 (50.0) | 0 |
| 4 | 59 (66.3) | 23 (79.3) | 28 (63.6) | 7 (53.8) | 0 | 1 (100.0) |
|
DOTATOC Krenning Score
|
|||||
|---|---|---|---|---|---|
| FDG Scan | 0 | 1 | 2 | 3 | 4 |
| Negative | 6 (85.7) | 1 (50.0) | 3 (42.9) | 34 (58.6) | 32 (35.2) |
| Positive | 1 (14.3) | 1 (50.0) | 4 (57.1) | 24 (41.4) | 59 (64.8) |
| Parameter | Total | Grade 1 | Grade 2 | Grade 3, Well Diff. |
|---|---|---|---|---|
| DOTATOC Scan Results | ||||
| Positive -> Negative | 1 (7.7) | 0 | 1 (14.3) | 0 |
| Positive -> Positive | 10 (76.9) | 4 (80.0) | 5 (71.4) | 1 (100.0) |
| Positive -> Positive -> Positive -> Positive | 2 (15.4) | 1 (20.0) | 1 (14.3) | 0 |
| FDG Scan Results | ||||
| Negative -> Negative | 2 (20.0) | 2 (40.0) | 0 | 0 |
| Positive -> Negative | 1 (10.0) | 1 (20.0) | 0 | 0 |
| Positive -> Positive | 6 (60.0) | 2 (40.0) | 3 (75.0) | 1 (100.0) |
| Positive -> Positive -> Positive | 1 (10.0) | 0 | 1 (25.0) | 0 |
| DOTATOC Scan Result | FDG Scan Result | n |
|---|---|---|
| Positive -> Negative | Negative | 1 |
| Positive -> Positive | Negative | 1 |
| Positive -> Positive | Negative -> Negative | 2 |
| Positive -> Positive | Positive | 1 |
| Positive -> Positive | Positive -> Positive | 6 |
| Positive -> Positive -> Positive -> Positive | Positive -> Negative | 1 |
| Positive -> Positive -> Positive -> Positive | Positive -> Positive -> Positive | 1 |
Patients with unknown grade and poorly differentiated grade 3 tumours are excluded in the survival analysis. Cases with Krenning score of 0 and 1 are combined into one group.
osdata_gepnet_met <- gepnet_met_data %>%
filter(grade_final != "Unknown Grade",
grade_final != "Grade 3, Poorly Diff.") %>%
droplevels() %>%
mutate(krenning_dot1 = if_else(krenning_dot1 %in% c(1, 0), "1 or 0",
as.character(krenning_dot1)) %>%
factor(levels = c("4", "3", "2", "1 or 0")))
The same population from the last step was used in multivariate
analysis.
The parameters of interest included grade (Grade 1 / Grade 2 / Grade 3,
Well Diff.), age (≤60 / >60), primary site (Pancreas / Small
Intestine / Other), baseline DOTA PET assessment (Positive / Negative),
baseline DOTA PET Krenning score (4 / 3 / 2 / 1 / 0), and baseline FDG
PET assessment (Positive / Negative).
Likelihood ratio test was applied to each parameter individually
first.
Parameters tentatively entered into the multivariate model if the test
result showed p<0.05.
With each new parameter entered into the model, the proportional hazard
assumption of the updated model and Schoenfeld residuals were checked to
ensure no time-dependent coefficient existed and the proportional hazard
assumption remained valid.
The variable was either removed or the model was adjusted when the
p-value in the assumption check was less than 0.05.
A total of three variables were expected to be included in the model due
to the number of events that occurred in the population to minimize the
probability of overfitting.
multi_var_gepnet_met <- osdata_gepnet_met %>%
select(study_id, status, survival_year, diag_prim_site, grade_final, age_at_diagnosis,
overall_assess_dot1, num_lesion_dot1, krenning_dot1, overall_assess_fdg1, num_lesion_fdg1) %>%
mutate(age_group = if_else(age_at_diagnosis > 60, ">60", "\u226460") %>%
factor(levels = c("\u226460", ">60")),
prim_site_group = case_when(diag_prim_site == "Pancreas" ~ "Pancreas",
diag_prim_site == "Small Intestine" ~ "Small Intestine",
TRUE ~ "Other") %>%
factor(levels = c("Pancreas", "Small Intestine", "Other")),
dot1_les_group = case_when(num_lesion_dot1 == "0" ~ "0 (DOTATOC Negative)",
num_lesion_dot1 %in% c("1", "2", "3", "4", "5") ~ "1-5",
num_lesion_dot1 == "6-10" ~ "6-10",
num_lesion_dot1 == ">10" ~ ">10") %>%
factor(levels = c(">10", "6-10", "1-5", "0 (DOTATOC Negative)")),
fdg1_les_group = case_when(num_lesion_fdg1 == "0" ~ "0 (FDG Negative)",
num_lesion_fdg1 %in% c("1", "2", "3", "4", "5") ~"1-5",
num_lesion_fdg1 == "6-10" ~ "6-10",
num_lesion_fdg1 == ">10" ~ ">10") %>%
factor(levels = c(">10", "6-10", "1-5", "0 (FDG Negative)"))) %>%
droplevels()
Variables to consider (first option used as reference):
## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final, data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## grade_finalGrade 2 1.6057 4.9811 0.5096 3.151 0.001627
## grade_finalGrade 3, Well Diff. 2.2531 9.5168 0.6102 3.693 0.000222
##
## Likelihood ratio test=18.67 on 2 df, p=8.841e-05
## n= 161, number of events= 31
## Call:
## coxph(formula = Surv(survival_year, status) ~ age_group, data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## age_group>60 1.0213 2.7768 0.4018 2.542 0.011
##
## Likelihood ratio test=6.99 on 1 df, p=0.008185
## n= 161, number of events= 31
## Call:
## coxph(formula = Surv(survival_year, status) ~ prim_site_group,
## data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## prim_site_groupSmall Intestine -0.48589 0.61515 0.44124 -1.101 0.271
## prim_site_groupOther 0.05458 1.05610 0.50297 0.109 0.914
##
## Likelihood ratio test=2.02 on 2 df, p=0.3648
## n= 161, number of events= 31
## Call:
## coxph(formula = Surv(survival_year, status) ~ overall_assess_dot1,
## data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## overall_assess_dot1Positive 1.709e+01 2.634e+07 4.095e+03 0.004 0.997
##
## Likelihood ratio test=3.06 on 1 df, p=0.0804
## n= 161, number of events= 31
## Call:
## coxph(formula = Surv(survival_year, status) ~ dot1_les_group,
## data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z
## dot1_les_group6-10 -4.318e-01 6.493e-01 6.155e-01 -0.702
## dot1_les_group1-5 -1.068e+00 3.438e-01 6.143e-01 -1.738
## dot1_les_group0 (DOTATOC Negative) -1.735e+01 2.922e-08 4.207e+03 -0.004
## p
## dot1_les_group6-10 0.4829
## dot1_les_group1-5 0.0822
## dot1_les_group0 (DOTATOC Negative) 0.9967
##
## Likelihood ratio test=7.16 on 3 df, p=0.06698
## n= 161, number of events= 31
## Call:
## coxph(formula = Surv(survival_year, status) ~ krenning_dot1,
## data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## krenning_dot13 -3.781e-01 6.852e-01 4.164e-01 -0.908 0.364
## krenning_dot12 1.143e-01 1.121e+00 1.029e+00 0.111 0.912
## krenning_dot11 or 0 -1.722e+01 3.318e-08 3.963e+03 -0.004 0.997
##
## Likelihood ratio test=4.25 on 3 df, p=0.2358
## n= 161, number of events= 31
## Call:
## coxph(formula = Surv(survival_year, status) ~ overall_assess_fdg1,
## data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## overall_assess_fdg1Positive 1.4393 4.2178 0.4898 2.938 0.0033
##
## Likelihood ratio test=11.4 on 1 df, p=0.0007341
## n= 161, number of events= 31
## Call:
## coxph(formula = Surv(survival_year, status) ~ fdg1_les_group,
## data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## fdg1_les_group6-10 -0.6015 0.5480 0.6682 -0.900 0.368020
## fdg1_les_group1-5 -0.6984 0.4974 0.4282 -1.631 0.102897
## fdg1_les_group0 (FDG Negative) -1.9327 0.1448 0.5591 -3.457 0.000547
##
## Likelihood ratio test=13.93 on 3 df, p=0.003003
## n= 161, number of events= 31
Model 1: Grade + Age + Baseline FDG Assessment
## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + age_group +
## overall_assess_fdg1, data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## grade_finalGrade 2 1.2027 3.3292 0.5163 2.330 0.01982
## grade_finalGrade 3, Well Diff. 1.9831 7.2652 0.6350 3.123 0.00179
## age_group>60 0.9821 2.6702 0.4382 2.241 0.02500
## overall_assess_fdg1Positive 1.0546 2.8709 0.5066 2.082 0.03737
##
## Likelihood ratio test=29.27 on 4 df, p=6.903e-06
## n= 161, number of events= 31
Check PH assumption:
## chisq df p
## grade_final 3.8789 2 0.14
## age_group 1.4136 1 0.23
## overall_assess_fdg1 0.0344 1 0.85
## GLOBAL 5.2170 4 0.27
| Variable | HR (95% CI) | P |
|---|---|---|
| Grade | ||
| Grade 1 (ref) | – | – |
| Grade 2 | 3.329 (1.210-9.158) | 0.0198 |
| Grade 3, Well Diff. | 7.265 (2.093-25.220) | 0.00179 |
| Age | ||
| ≤60 (ref) | – | – |
| >60 | 2.670 (1.131-6.303) | 0.0250 |
| Baseline FDG Assessment | ||
| Negative (ref) | – | – |
| Positive | 2.871 (1.064-7.749) | 0.0374 |
Model 2: Grade + Age + Baseline FDG Number of Lesions
## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + age_group +
## fdg1_les_group, data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z p
## grade_finalGrade 2 1.28163 3.60251 0.55076 2.327 0.01996
## grade_finalGrade 3, Well Diff. 1.64233 5.16721 0.71555 2.295 0.02172
## age_group>60 1.43658 4.20629 0.51416 2.794 0.00521
## fdg1_les_group6-10 -0.09468 0.90967 0.76610 -0.124 0.90165
## fdg1_les_group1-5 -1.05782 0.34721 0.58138 -1.819 0.06884
## fdg1_les_group0 (FDG Negative) -1.84617 0.15784 0.68142 -2.709 0.00674
##
## Likelihood ratio test=33.16 on 6 df, p=9.768e-06
## n= 161, number of events= 31
Check PH assumption:
## chisq df p
## grade_final 5.47 2 0.065
## age_group 1.49 1 0.223
## fdg1_les_group 9.33 3 0.025
## GLOBAL 9.55 6 0.145
Model 3: Grade + Age + Baseline FDG Assessment + Baseline DOTATOC Number of Lesions
## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + age_group +
## overall_assess_fdg1 + dot1_les_group, data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z
## grade_finalGrade 2 1.205e+00 3.338e+00 5.174e-01 2.329
## grade_finalGrade 3, Well Diff. 1.909e+00 6.746e+00 6.329e-01 3.016
## age_group>60 9.747e-01 2.650e+00 4.434e-01 2.198
## overall_assess_fdg1Positive 9.001e-01 2.460e+00 5.183e-01 1.737
## dot1_les_group6-10 -3.226e-01 7.243e-01 6.297e-01 -0.512
## dot1_les_group1-5 -4.921e-01 6.114e-01 6.405e-01 -0.768
## dot1_les_group0 (DOTATOC Negative) -1.634e+01 7.994e-08 4.658e+03 -0.004
## p
## grade_finalGrade 2 0.01984
## grade_finalGrade 3, Well Diff. 0.00256
## age_group>60 0.02795
## overall_assess_fdg1Positive 0.08243
## dot1_les_group6-10 0.60844
## dot1_les_group1-5 0.44236
## dot1_les_group0 (DOTATOC Negative) 0.99720
##
## Likelihood ratio test=31.05 on 7 df, p=6.086e-05
## n= 161, number of events= 31
Check PH assumption:
## chisq df p
## grade_final 3.9456 2 0.14
## age_group 1.6109 1 0.20
## overall_assess_fdg1 0.0513 1 0.82
## dot1_les_group 3.3560 3 0.34
## GLOBAL 8.9848 7 0.25
| Variable | HR (95% CI) | P |
|---|---|---|
| Grade | ||
| Grade 1 (ref) | – | – |
| Grade 2 | 3.338 (1.211-9.202) | 0.0198 |
| Grade 3, Well Diff. | 6.746 (1.951-23.325) | 0.00256 |
| Age | ||
| ≤60 (ref) | – | – |
| >60 | 2.650 (1.111-6.321) | 0.0280 |
| Baseline FDG Assessment | ||
| Negative (ref) | – | – |
| Positive | 2.460 (0.891-6.793) | 0.0824 |
| Baseline DOTATOC Number of Lesions | ||
| >10 (ref) | – | – |
| 6-10 | 0.724 (0.211-2.488) | 0.608 |
| 1-5 | 0.611 (0.174-2.145) | 0.442 |
| 0 (DOTATOC Negative) | 0.000 (0.000-Inf) | 0.997 |
Model 4: Grade + Age + Baseline DOTATOC Number of Lesions
## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + age_group +
## dot1_les_group, data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z
## grade_finalGrade 2 1.263e+00 3.536e+00 5.223e-01 2.418
## grade_finalGrade 3, Well Diff. 2.151e+00 8.594e+00 6.289e-01 3.421
## age_group>60 9.741e-01 2.649e+00 4.474e-01 2.177
## dot1_les_group6-10 -3.911e-01 6.763e-01 6.256e-01 -0.625
## dot1_les_group1-5 -7.760e-01 4.603e-01 6.277e-01 -1.236
## dot1_les_group0 (DOTATOC Negative) -1.680e+01 5.055e-08 4.714e+03 -0.004
## p
## grade_finalGrade 2 0.015591
## grade_finalGrade 3, Well Diff. 0.000625
## age_group>60 0.029445
## dot1_les_group6-10 0.531903
## dot1_les_group1-5 0.216413
## dot1_les_group0 (DOTATOC Negative) 0.997156
##
## Likelihood ratio test=27.56 on 6 df, p=0.0001137
## n= 161, number of events= 31
Check PH assumption:
## chisq df p
## grade_final 4.16 2 0.12
## age_group 1.77 1 0.18
## dot1_les_group 3.94 3 0.27
## GLOBAL 10.18 6 0.12
| Variable | HR (95% CI) | P |
|---|---|---|
| Grade | ||
| Grade 1 (ref) | – | – |
| Grade 2 | 3.536 (1.270-9.841) | 0.0156 |
| Grade 3, Well Diff. | 8.594 (2.506-29.477) | |
| Age | ||
| ≤60 (ref) | – | – |
| >60 | 2.649 (1.102-6.366) | 0.0294 |
| Baseline DOTATOC Number of Lesions | ||
| >10 (ref) | – | – |
| 6-10 | 0.676 (0.198-2.305) | 0.532 |
| 1-5 | 0.460 (0.134-1.575) | 0.216 |
| 0 (DOTATOC Negative) | 0.000 (0.000-Inf) | 0.997 |
Model 5: Grade + Baseline FDG Assessment + Baseline DOTATOC Number of Lesions
## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + overall_assess_fdg1 +
## dot1_les_group, data = multi_var_gepnet_met)
##
## coef exp(coef) se(coef) z
## grade_finalGrade 2 1.407e+00 4.085e+00 5.118e-01 2.749
## grade_finalGrade 3, Well Diff. 1.763e+00 5.828e+00 6.266e-01 2.813
## overall_assess_fdg1Positive 8.971e-01 2.452e+00 5.227e-01 1.716
## dot1_les_group6-10 -1.928e-01 8.246e-01 6.288e-01 -0.307
## dot1_les_group1-5 -3.908e-01 6.765e-01 6.436e-01 -0.607
## dot1_les_group0 (DOTATOC Negative) -1.669e+01 5.646e-08 4.434e+03 -0.004
## p
## grade_finalGrade 2 0.00597
## grade_finalGrade 3, Well Diff. 0.00491
## overall_assess_fdg1Positive 0.08615
## dot1_les_group6-10 0.75908
## dot1_les_group1-5 0.54372
## dot1_les_group0 (DOTATOC Negative) 0.99700
##
## Likelihood ratio test=25.72 on 6 df, p=0.0002515
## n= 161, number of events= 31
Check PH assumption:
## chisq df p
## grade_final 4.641 2 0.098
## overall_assess_fdg1 0.116 1 0.733
## dot1_les_group 4.277 3 0.233
## GLOBAL 8.586 6 0.198
| Variable | HR (95% CI) | P |
|---|---|---|
| Grade | ||
| Grade 1 (ref) | – | – |
| Grade 2 | 4.085 (1.498-11.139) | 0.00597 |
| Grade 3, Well Diff. | 5.828 (1.707-19.901) | 0.00491 |
| Baseline FDG Assessment | ||
| Negative (ref) | – | – |
| Positive | 2.452 (0.880-6.832) | 0.0862 |
| Baseline DOTATOC Number of Lesions | ||
| >10 (ref) | – | – |
| 6-10 | 0.825 (0.240-2.828) | 0.759 |
| 1-5 | 0.677 (0.192-2.389) | 0.544 |
| 0 (DOTATOC Negative) | 0.000 (0.000-Inf) | 0.997 |
The final multivariate model consisted of grade, age, and baseline FDG
assessment (Model 1).